#include "encfile.h"

Encfile::Encfile(string path, int uid, QString encdir,  QString usrName, QString fformat = "")
{
    originPath = path;
    usrId = uid;
    dir = encdir ;
    format = fformat;
    int pos = path.find_last_of('/');
    filename = path.substr(pos + 1);
    relativePath = QCoreApplication::applicationDirPath();
    con = new sqlConn(relativePath+"/settings/conf.ini");
    usrname = usrName;
    string digest;
    Weak::MD5 md5;
    StringSource(usrName.toStdString(), true, new HashFilter(md5, new HexEncoder(new StringSink(digest))));
    encFilename = QString::fromStdString(&digest);

}

Encfile::Encfile(int iid , QString usrName)
{
    id = iid;
    usrname = usrName;
    con = new sqlConn(relativePath+"/settings/conf.ini");
    queryInfo();
}

void Encfile::create()
{
  if(path)
  {
      // get plain
      long l,m;
      long bfsize;
      long cipherlen = 0;
      ifstream fs(originPath , ios::in|ios::binary);
      ofstream of(relativePath.append("/files/").append(encFilename).append(".tmp").toStdString() ,
                  ios::out | ios::binary);
      l = fs.tellg();
      fs.seekg (0, ios::end);
      m = fs.tellg();
      fs.seekg (0, ios::beg);
      size = m - l ;
      while(size > 0)
      {
          bfsize = size > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : size ;
          // get buffer
          char * buffer = new char [bfsize + 1];
          fs.read(buffer, bfsize);
          string plain = buffer ;

          // gene cipher
          const char* cipher = RSAEncryptString(plain).c_str();
          delete(plain);
          //write file
          cipherlen += strlen(cipher);
          of.write(cipher, strlen(cipher));
          size -= bfsize ;
      }
      fs.close();
      of.close();
      //search sql to find space
      findDataSpace(cipherlen);
      //write to sbFS
      if(startptr)
      {
          ofstream os(relativePath.append("/files/").append(encFilename).append(".sbfs").toStdString() ,
                      ios::out |ios::binary);
          ifstream is(relativePath.append("/files/").append(encFilename).append(".tmp").toStdString() ,
                      ios::in | ios::binary);
          os.seekp(0,startptr);
          while(cipherlen > 0)
          {
              bfsize = cipherlen > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : cipherlen ;
              char * buffer = new char [bfsize + 1];
              is.read(buffer, bfsize);
              os.write(buffer, bfsize);
              cipherlen -= bfsize ;
          }
          os.close();
          is.clear();
      }
      remove(originPath.c_str());
      remove(relativePath.append("/files/").append(encFilename).append(".tmp").toStdString().c_str());
  }
}

string Encfile::RSAEncryptString(string plain)
{
    string Ciphertext;
    StringSource PublicKey(strPub, true, new HexDecoder);
    RSAES_OAEP_SHA_Encryptor Pub(PublicKey);
    const char Seed[1024] = "sbox";
    RandomPool RandPool;
    RandPool.IncorporateEntropy((byte *)Seed, strlen(Seed));
    int MaxMsgLength = Pub.FixedMaxPlaintextLength();
    for (int i = plain.size(), j=0; i > 0; i -= MaxMsgLength, j += MaxMsgLength)
    {
        string PartPlaintext = plain.substr(j, MaxMsgLength);
        string PartCiphertext;
        StringSource(PartPlaintext, true, new PK_EncryptorFilter(RandPool, Pub, new HexEncoder(new StringSink(PartCiphertext))));
        Ciphertext += PartCiphertext;
    }
    return Ciphertext;
}

string Encfile::RSADecryptString(string cipher)
{
    string Plaintext;
    StringSource PrivKey(strPriv, true, new HexDecoder);
    RSAES_OAEP_SHA_Decryptor Priv(PrivKey);
    //indicate the ciphertext in hexcode
    int CiphertextLength = Priv.FixedCiphertextLength() * 2;
    for (int i = cipher.size(), j=0; i > 0; i -= CiphertextLength, j += CiphertextLength)
    {
        string PartCiphertext = cipher.substr(j, CiphertextLength);
        string PartPlaintext;
        StringSource(PartCiphertext, true, new HexDecoder(new PK_DecryptorFilter(RNG(), Priv, new StringSink(PartPlaintext))));
        Plaintext += PartPlaintext;
    }
    return Plaintext;
}

void Encfile::generateRSAKeys()
{
    //public key
    long l, m;
    fstream fs(relativePath.append("/Users/").append(usrname).append(".pbk").toStdString() ,
               ios::in | ios::binary);
    l = fs.tellg();
    fs.seekg (0, ios::end);
    m = fs.tellg();
    fs.seekg (0, ios::beg);
    char *c_strPub = new char [m - l + 1];
    fs.read(c_strPub, m - l);
    strPub = c_strPub;
    free(c_strPub);
    fs.close();

    //private key
    QString priv;
    con->connect();
    QSqlQuery *query = con->queryData(QString("SELECT strPriv FROM userprofile WHERE id=").append(usrId));
    if(query->next())
    {
        priv = query->value(0);
    }
    strPriv = priv.toStdString();
    con->close();
}

void Encfile::queryInfo()
{
    con->connect();
    QString sqlstr = "SELECT * FROM files WHERE item_id='";
    sqlstr.append(id).append("'");
    QSqlQuery* query = con->queryData(sqlstr);
    if(query->next())
    {
        usrId = query->value(1);
        dir = query->value(2);
        filename =query->value(3);
        encFilename = query->value(4);
        size = query->value(5);
        startptr = query->value(6);
        endptr = query->value(7);
        format = query->value(8);
    }
    con->close();
}

void Encfile::findDataSpace(long length)
{
    con->connect();
    QString sqlstr = "SELECT * FROM stacks WHERE size>=";
    sqlstr.append(length).append(" AND usr_id=").append(usrId);
    QSqlQuery* query = con->queryData(sqlstr);
    if(query->next())
    {
        startptr = query->value(3);
        endptr = startptr + length;
        long s_endptr = query->value(5);
        if(length == s_endptr)
            con->saveData(QString("DELETE FROM stacks WHERE id=").append(query->value(0)));
        else
            con->saveData(QString("UPDATE stacks SET startptr=").append(endptr + 1)
                          .append(", size=").append(s_endptr - length).append(" WHERE id=")
                          .append(query->value(0)));
    }
    QSqlQuery q;
    q.prepare("INSERT INTO files (usr_id, dir, filename, encrypt_name, size, startptr, endptr, format) "
              "VALUES (:usr_id, :dir, :filename, :encrypt_name,:size,:startptr,:endptr,:format)");
    q.addBindValue(":usr_id", usrId);
    q.addBindValue(":dir", dir);
    q.addBindValue(":filename", filename);
    q.addBindValue(":encrypt_name", encFilename);
    q.addBindValue(":size", size);
    q.addBindValue(":startptr", startptr);
    q.addBindValue(":endptr", endptr);
    q.addBindValue(":format", format);
    q.exec();
    con->close();
}

RandomPool &Encfile::RNG()
{
    return m_sRandPool;
}

RandomPool Encfile::m_sRandPool;
